Passed
Push — task/security-fixes ( 83e62e...beb66e )
by Grant
04:25
created

ApplicationReviewWithNav.handleStatusChange   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
import React from "react";
2
import {
3
  injectIntl,
4
  WrappedComponentProps,
5
  FormattedMessage,
6
} from "react-intl";
7
import className from "classnames";
8
import Swal, { SweetAlertResult } from "sweetalert2";
9
import * as routes from "../../helpers/routes";
10
import Select, { SelectOption } from "../Select";
11
import { Application } from "../../models/types";
12
import { ReviewStatusId } from "../../models/lookupConstants";
13
import { Portal } from "../../models/app";
14
import { messages } from "./ApplicationReview";
15
16
interface ApplicationReviewWithNavProps {
17
  application: Application;
18
  reviewStatusOptions: SelectOption[];
19
  onStatusChange: (
20
    applicationId: number,
21
    statusId: number | null,
22
  ) => Promise<void>;
23
  onNotesChange: (applicationId: number, notes: string | null) => void;
24
  isSaving: boolean;
25
  portal: Portal;
26
}
27
28
interface ApplicationReviewWithNavState {
29
  selectedStatusId: number | undefined;
30
}
31
32
class ApplicationReviewWithNav extends React.Component<
33
  ApplicationReviewWithNavProps & WrappedComponentProps,
34
  ApplicationReviewWithNavState
35
> {
36
  public constructor(
37
    props: ApplicationReviewWithNavProps & WrappedComponentProps,
38
  ) {
39
    super(props);
40
    this.state = {
41
      selectedStatusId:
42
        props.application.application_review &&
43
        props.application.application_review.review_status_id
44
          ? props.application.application_review.review_status_id
45
          : undefined,
46
    };
47
    this.handleStatusChange = this.handleStatusChange.bind(this);
48
    this.handleSaveClicked = this.handleSaveClicked.bind(this);
49
    this.showNotes = this.showNotes.bind(this);
50
    this.handleLinkClicked = this.handleLinkClicked.bind(this);
51
    this.isUnchanged = this.isUnchanged.bind(this);
52
  }
53
54
  /**
55
   * Returns true only if selectedStatusId matches the review
56
   * status of props.application
57
   */
58
  protected isUnchanged = (): boolean => {
59
    const { application } = this.props;
60
    const { selectedStatusId } = this.state;
61
    if (
62
      application.application_review &&
63
      application.application_review.review_status_id
64
    ) {
65
      return (
66
        application.application_review.review_status_id === selectedStatusId
67
      );
68
    }
69
    return selectedStatusId === undefined;
70
  };
71
72
  /**
73
   * When save is clicked, it is only necessary to save the status
74
   * @param event
75
   */
76
  protected handleSaveClicked(): Promise<void> {
77
    const { selectedStatusId } = this.state;
78
    const { application, onStatusChange, intl } = this.props;
79
    const status = selectedStatusId || null;
80
81
    const sectionChange = (
82
      oldStatus: number | null,
83
      newStatus: number | null,
84
    ): boolean => {
85
      const oldIsScreenedOut: boolean =
86
        oldStatus === ReviewStatusId.ScreenedOut;
87
      const newIsScreenedOut: boolean =
88
        newStatus === ReviewStatusId.ScreenedOut;
89
      return oldIsScreenedOut !== newIsScreenedOut;
90
    };
91
    const oldStatus = application.application_review
92
      ? application.application_review.review_status_id
93
      : null;
94
    if (sectionChange(oldStatus, status)) {
95
      const confirmText =
96
        status === ReviewStatusId.ScreenedOut
97
          ? intl.formatMessage(messages.screenOutConfirm)
98
          : intl.formatMessage(messages.screenInConfirm);
99
      return Swal.fire({
100
        title: confirmText,
101
        icon: "question",
102
        showCancelButton: true,
103
        confirmButtonColor: "#0A6CBC",
104
        cancelButtonColor: "#F94D4D",
105
        confirmButtonText: intl.formatMessage(messages.confirmButton),
106
        cancelButtonText: intl.formatMessage(messages.cancelButton),
107
      }).then((result: SweetAlertResult) => {
108
        if (result.value) {
109
          return onStatusChange(application.id, status);
110
        }
111
        return Promise.resolve();
112
      });
113
    }
114
    return onStatusChange(application.id, status);
115
  }
116
117
  protected showNotes(): void {
118
    const { application, onNotesChange, intl } = this.props;
119
    const notes =
120
      application.application_review && application.application_review.notes
121
        ? application.application_review.notes
122
        : "";
123
    Swal.fire({
124
      title: intl.formatMessage(messages.editNote),
125
      icon: "question",
126
      input: "textarea",
127
      showCancelButton: true,
128
      confirmButtonColor: "#0A6CBC",
129
      cancelButtonColor: "#F94D4D",
130
      cancelButtonText: intl.formatMessage(messages.cancelButton),
131
      confirmButtonText: intl.formatMessage(messages.save),
132
      inputValue: notes,
133
    }).then((result: SweetAlertResult) => {
134
      if (result && result.value !== undefined) {
135
        const value = result.value ? result.value : null;
136
        onNotesChange(application.id, value);
137
      }
138
    });
139
  }
140
141
  protected handleLinkClicked(url: string): void {
142
    if (this.isUnchanged()) {
143
      window.location.href = url;
144
      return;
145
    }
146
    this.handleSaveClicked()
147
      .then(() => {
148
        window.location.href = url;
149
      })
150
      .catch(() => {
151
        // do not navigate away from page
152
      });
153
  }
154
155
  protected handleStatusChange(
156
    event: React.ChangeEvent<HTMLSelectElement>,
157
  ): void {
158
    const value =
159
      event.target.value && !Number.isNaN(Number(event.target.value))
160
        ? Number(event.target.value)
161
        : undefined;
162
    this.setState({ selectedStatusId: value });
163
  }
164
165
  public render(): React.ReactElement {
166
    const {
167
      application,
168
      reviewStatusOptions,
169
      isSaving,
170
      intl,
171
      portal,
172
    } = this.props;
173
    const l10nReviewStatusOptions = reviewStatusOptions.map((status) => ({
174
      value: status.value,
175
      label: intl.formatMessage(messages[status.label]),
176
    }));
177
    const { selectedStatusId } = this.state;
178
    const reviewStatus =
179
      application.application_review &&
180
      application.application_review.review_status
181
        ? application.application_review.review_status.name
182
        : null;
183
    const statusIconClass = className("fas", {
184
      "fa-ban": reviewStatus === "screened_out",
185
      "fa-question-circle": reviewStatus === "still_thinking",
186
      "fa-check-circle": reviewStatus === "still_in",
187
      "fa-exclamation-circle": reviewStatus === null,
188
    });
189
    const applicantUrlMap: { [key in typeof portal]: string } = {
190
      hr: routes.hrApplicantShow(
191
        intl.locale,
192
        application.applicant_id,
193
        application.job_poster_id,
194
      ),
195
      manager: routes.managerApplicantShow(
196
        intl.locale,
197
        application.applicant_id,
198
        application.job_poster_id,
199
      ),
200
    };
201
    const applicationUrlMap: { [key in typeof portal]: string } = {
202
      hr: routes.hrApplicationShow(
203
        intl.locale,
204
        application.id,
205
        application.job_poster_id,
206
      ),
207
      manager: routes.managerApplicationShow(
208
        intl.locale,
209
        application.id,
210
        application.job_poster_id,
211
      ),
212
    };
213
    const jobUrlMap: { [key in typeof portal]: string } = {
214
      hr: routes.hrJobPreview(intl.locale, application.job_poster_id),
215
      manager: routes.managerJobPreview(intl.locale, application.job_poster_id),
216
    };
217
    const jobApplicationsUrlMap: { [key in typeof portal]: string } = {
218
      hr: routes.hrJobApplications(intl.locale, application.job_poster_id),
219
      manager: routes.managerJobApplications(
220
        intl.locale,
221
        application.job_poster_id,
222
      ),
223
    };
224
    const applicantUrl = applicantUrlMap[portal];
225
    const applicationUrl = applicationUrlMap[portal];
226
    const jobUrl = jobUrlMap[portal];
227
    const jobApplicationsUrl = jobApplicationsUrlMap[portal];
228
229
    const getSaveButtonText = (): string => {
230
      if (isSaving) {
231
        return intl.formatMessage(messages.saving);
232
      }
233
      if (this.isUnchanged()) {
234
        return intl.formatMessage(messages.saved);
235
      }
236
      return intl.formatMessage(messages.save);
237
    };
238
    const saveButtonText = getSaveButtonText();
239
    const noteButtonText =
240
      application.application_review && application.application_review.notes
241
        ? intl.formatMessage(messages.editNote)
242
        : intl.formatMessage(messages.addNote);
243
    return (
244
      <div>
245
        <div>
246
          <div className="manager-application-preview-actions flex-grid">
247
            <div className="box small-1of3">
248
              <button
249
                className="button--blue light-bg"
250
                type="button"
251
                onClick={() => this.handleLinkClicked(jobApplicationsUrl)}
252
              >
253
                {`< `}
254
                <FormattedMessage
255
                  id="application.review.backToApplicantList"
256
                  defaultMessage="Save and Go Back to Applicant List"
257
                  description="Back Button text"
258
                />
259
              </button>
260
            </div>
261
            <div className="box small-2of3">
262
              <a
263
                className="button--blue light-bg"
264
                href={jobUrl}
265
                style={{ marginRight: ".5rem" }}
266
              >
267
                <FormattedMessage
268
                  id="application.review.button.viewJobPoster"
269
                  defaultMessage="View Job Poster"
270
                  description="View Job Poster Button text"
271
                />
272
              </a>
273
              <button
274
                className="button--blue light-bg"
275
                data-button-type="expand-all"
276
                type="button"
277
                id="expand-all"
278
              >
279
                <span className="expand">
280
                  {" "}
281
                  <FormattedMessage
282
                    id="application.review.expandAllSkills"
283
                    defaultMessage="Expand All Skills"
284
                    description="Expand All Skills Button text"
285
                  />
286
                </span>
287
                <span className="collapse">
288
                  {" "}
289
                  <FormattedMessage
290
                    id="application.review.collapseAllSkills"
291
                    defaultMessage="Collapse All Skills"
292
                    description="Collapse All Skills Button text"
293
                  />
294
                </span>
295
              </button>
296
            </div>
297
          </div>
298
        </div>
299
300
        <form className="applicant-summary">
301
          <div className="flex-grid middle gutter">
302
            <div className="box lg-1of11 applicant-status">
303
              <i className={statusIconClass} />
304
            </div>
305
306
            <div className="box lg-2of11 applicant-information">
307
              <span className="name">
308
                {application.applicant.user.first_name}{" "}
309
                {application.applicant.user.last_name}
310
              </span>
311
              <a
312
                href={`mailto: ${application.applicant.user.email}`}
313
                title={intl.formatMessage(messages.emailCandidate)}
314
              >
315
                {application.applicant.user.email}
316
              </a>
317
              {/* This span only shown for veterans */}
318
              {(application.veteran_status.name === "current" ||
319
                application.veteran_status.name === "past") && (
320
                <span className="veteran-status">
321
                  <img
322
                    alt={intl.formatMessage(messages.viewApplicationTitle)}
323
                    src={routes.imageUrl("icon_veteran.svg")}
324
                  />{" "}
325
                  {intl.formatMessage(messages.veteranStatus)}
326
                </span>
327
              )}
328
            </div>
329
330
            <div className="box lg-2of11 applicant-links">
331
              <a
332
                title={intl.formatMessage(messages.viewApplicationTitle)}
333
                href={applicationUrl}
334
              >
335
                <i className="fas fa-file-alt" />
336
                {intl.formatMessage(messages.viewApplicationText)}
337
              </a>
338
              <a
339
                title={intl.formatMessage(messages.viewProfileTitle)}
340
                href={applicantUrl}
341
              >
342
                <i className="fas fa-user" />
343
                {intl.formatMessage(messages.viewProfileText)}
344
              </a>
345
            </div>
346
347
            <div className="box lg-2of11 applicant-decision" data-clone>
348
              <Select
349
                id={`review_status_${application.id}`}
350
                name="review_status"
351
                label={intl.formatMessage(messages.decision)}
352
                required={false}
353
                selected={selectedStatusId || null}
354
                nullSelection={intl.formatMessage(messages.notReviewed)}
355
                options={l10nReviewStatusOptions}
356
                onChange={this.handleStatusChange}
357
              />
358
            </div>
359
360
            <div className="box lg-2of11 applicant-notes">
361
              <button
362
                className="button--outline"
363
                type="button"
364
                onClick={this.showNotes}
365
              >
366
                {noteButtonText}
367
              </button>
368
            </div>
369
370
            <div className="box lg-2of11 applicant-save-button">
371
              <button
372
                className="button--blue light-bg"
373
                type="button"
374
                onClick={() => this.handleSaveClicked()}
375
              >
376
                <span>{saveButtonText}</span>
377
              </button>
378
            </div>
379
          </div>
380
        </form>
381
      </div>
382
    );
383
  }
384
}
385
386
export default injectIntl(ApplicationReviewWithNav);
387